/*
 * Copyright 2011 Karl Pauls karlpauls@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.kalpatec.pojosr.framework;

import java.io.File;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.Version;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.packageadmin.RequiredBundle;
import org.osgi.service.startlevel.StartLevel;

import de.kalpatec.pojosr.framework.felix.framework.ServiceRegistry;
import de.kalpatec.pojosr.framework.felix.framework.util.EventDispatcher;
import de.kalpatec.pojosr.framework.launch.BundleDescriptor;
import de.kalpatec.pojosr.framework.launch.ClasspathScanner;
import de.kalpatec.pojosr.framework.launch.PojoServiceRegistry;
import de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory;

public class PojoSR implements PojoServiceRegistry {
	private static final String POJOSR_FRAMEWORK = "de.kalpatec.pojosr.framework";

	private final BundleContext m_context;

	private final ServiceRegistry m_reg = new ServiceRegistry(
			new ServiceRegistry.ServiceRegistryCallbacks() {

				@Override
				public void serviceChanged(ServiceEvent event,
						Dictionary<String, ?> oldProps) {
					m_dispatcher.fireServiceEvent(event, oldProps, null);
				}
			});

	private final EventDispatcher m_dispatcher = new EventDispatcher(m_reg);
	private final Map<Long, Bundle> m_bundles = new HashMap<Long, Bundle>();
	private final Map<String, Bundle> m_symbolicNameToBundle = new HashMap<String, Bundle>();
	private final Map<String, ?> bundleConfig;

	public PojoSR(Map<String, ?> config) throws Exception {
		final Attributes headers = new Attributes();
		headers.put(new Attributes.Name(Constants.BUNDLE_SYMBOLICNAME),
				POJOSR_FRAMEWORK);
		headers.put(new Attributes.Name(Constants.BUNDLE_VERSION),
				"0.3.0-SNAPSHOT");
		headers.put(new Attributes.Name(Constants.BUNDLE_NAME), "System Bundle");
		headers.put(new Attributes.Name(Constants.BUNDLE_MANIFESTVERSION), "2");
		headers.put(new Attributes.Name(Constants.BUNDLE_VENDOR), "kalpatec");

		bundleConfig = new HashMap<String, Object>(config);

		final Bundle b = new PojoSRBundle(new Revision() {

			@Override
			public long getLastModified() {
				return System.currentTimeMillis();
			}

			@Override
			public Enumeration<String> getEntries() {
				java.util.List<String> e = java.util.Collections.emptyList();
				return java.util.Collections.enumeration(e);
			}

			@Override
			public URL getEntry(String entryName) {
				return getClass().getClassLoader().getResource(entryName);
			}
		}, headers, new Version(0, 0, 1), "file:pojosr", m_reg, m_dispatcher,
				null, 0, POJOSR_FRAMEWORK, m_bundles, getClass()
						.getClassLoader(), bundleConfig) {
			@Override
			public synchronized void start() throws BundleException {
				if (m_state != Bundle.RESOLVED) {
					return;
				}
				m_dispatcher.startDispatching();
				m_state = Bundle.STARTING;

				m_dispatcher.fireBundleEvent(new BundleEvent(
						BundleEvent.STARTING, this));
				m_context = new PojoSRBundleContext(this, m_reg, m_dispatcher,
						m_bundles, bundleConfig);
				int i = 0;
				for (Bundle b : m_bundles.values()) {
					i++;
					try {
						if (b != this) {
							b.start();
						}
					} catch (Throwable t) {
						System.out.println("Unable to start bundle: " + i);
						t.printStackTrace();
					}
				}
				m_state = Bundle.ACTIVE;
				m_dispatcher.fireBundleEvent(new BundleEvent(
						BundleEvent.STARTED, this));

				m_dispatcher.fireFrameworkEvent(new FrameworkEvent(
						FrameworkEvent.STARTED, this, null));
				super.start();
			};

			@Override
			public synchronized void stop() throws BundleException {
				if ((m_state == Bundle.STOPPING) || m_state == Bundle.RESOLVED) {
					return;

				} else if (m_state != Bundle.ACTIVE) {
					throw new BundleException(
							"Can't stop pojosr because it is not ACTIVE");
				}
				final Bundle systemBundle = this;
				Runnable r = new Runnable() {

					public void run() {
						m_dispatcher.fireBundleEvent(new BundleEvent(
								BundleEvent.STOPPING, systemBundle));
						for (Bundle b : m_bundles.values()) {
							try {
								if (b != systemBundle) {
									b.stop();
								}
							} catch (Throwable t) {
								t.printStackTrace();
							}
						}
						m_dispatcher.fireBundleEvent(new BundleEvent(
								BundleEvent.STOPPED, systemBundle));
						m_state = Bundle.RESOLVED;
						m_dispatcher.stopDispatching();
					}
				};
				m_state = Bundle.STOPPING;
				if ("true"
						.equalsIgnoreCase(System
								.getProperty("de.kalpatec.pojosr.framework.events.sync"))) {
					r.run();
				} else {
					new Thread(r).start();
				}
			}
		};
		m_symbolicNameToBundle.put(b.getSymbolicName(), b);
		b.start();
		b.getBundleContext().registerService(StartLevel.class.getName(),
				new StartLevel() {

					public void setStartLevel(int startlevel) {
					}

					public void setInitialBundleStartLevel(int startlevel) {
					}

					public void setBundleStartLevel(Bundle bundle,
							int startlevel) {
					}

					public boolean isBundlePersistentlyStarted(Bundle bundle) {
						return true;
					}

					public boolean isBundleActivationPolicyUsed(Bundle bundle) {
						return false;
					}

					public int getStartLevel() {
						return 1;
					}

					public int getInitialBundleStartLevel() {
						return 1;
					}

					public int getBundleStartLevel(Bundle bundle) {
						return 1;
					}
				}, null);

		b.getBundleContext().registerService(PackageAdmin.class.getName(),
				new PackageAdmin() {

					public boolean resolveBundles(Bundle[] bundles) {
						return true;
					}

					public void refreshPackages(Bundle[] bundles) {
						m_dispatcher.fireFrameworkEvent(new FrameworkEvent(
								FrameworkEvent.PACKAGES_REFRESHED, b, null));
					}

					public RequiredBundle[] getRequiredBundles(
							String symbolicName) {
						return null;
					}

					public Bundle[] getHosts(Bundle bundle) {
						return null;
					}

					public Bundle[] getFragments(Bundle bundle) {
						return null;
					}

					public ExportedPackage[] getExportedPackages(String name) {
						return null;
					}

					public ExportedPackage[] getExportedPackages(Bundle bundle) {
						return null;
					}

					public ExportedPackage getExportedPackage(String name) {
						return null;
					}

					public Bundle[] getBundles(String symbolicName,
							String versionRange) {
						Bundle result = m_symbolicNameToBundle
								.get((symbolicName != null) ? symbolicName
										.trim() : symbolicName);
						if (result != null) {
							return new Bundle[] { result };
						}
						return null;
					}

					public int getBundleType(Bundle bundle) {
						return 0;
					}

					@Override
					public Bundle getBundle(Class clazz) {
						return m_context.getBundle();
					}
				}, null);
		m_context = b.getBundleContext();

		@SuppressWarnings("unchecked")
		List<BundleDescriptor> scan = (List<BundleDescriptor>) config
				.get(PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS);

		if (scan != null) {
			startBundles(scan);
		}
	}

	/**
     * 
     */
	@Override
	public void startBundles(List<BundleDescriptor> scan) throws Exception {
		for (BundleDescriptor desc : scan) {

			Revision r = new URLRevision(desc.getURI(), -1);

			Attributes bundleHeaders = desc.getAttributes();

			// desc.print(System.out);

			Version osgiVersion = null;
			try {
				osgiVersion = Version.parseVersion(bundleHeaders
						.getValue(Constants.BUNDLE_VERSION));
			} catch (Exception ex) {
				ex.printStackTrace();
				osgiVersion = Version.emptyVersion;
			}
			String sym = bundleHeaders.getValue(Constants.BUNDLE_SYMBOLICNAME);
			if (sym != null) {
				int idx = sym.indexOf(';');
				if (idx > 0) {
					sym = sym.substring(0, idx);
				}
				sym = sym.trim();
			}

			if ((sym == null) || !m_symbolicNameToBundle.containsKey(sym)) {
				// TODO: framework - support multiple versions
				Bundle bundle = new PojoSRBundle(r,
						bundleHeaders,
						osgiVersion,
						desc.toString(), // desc.getUrl().toExternalForm(),
						m_reg, m_dispatcher,
						bundleHeaders.getValue(Constants.BUNDLE_ACTIVATOR),
						m_bundles.size(), sym, m_bundles,
						desc.getClassLoader(), bundleConfig);
				if (sym != null) {
					m_symbolicNameToBundle
							.put(bundle.getSymbolicName(), bundle);
				}
			}

		}

		for (long i = 1; i < m_bundles.size(); i++) {
			try {
				m_bundles.get(i).start();
			} catch (Throwable e) {
				System.out.println("Unable to start bundle: " + i);
				e.printStackTrace();
			}
		}

	}

	public static void main(String[] args) throws Exception {
		Filter filter = null;
		Class<?> main = null;
		for (int i = 0; (args != null) && (i < args.length) && (i < 2); i++) {
			try {
				filter = FrameworkUtil.createFilter(args[i]);
			} catch (InvalidSyntaxException ie) {
				try {
					main = PojoSR.class.getClassLoader().loadClass(args[i]);
				} catch (Exception ex) {
					throw new IllegalArgumentException(
							"Argument is neither a filter nor a class: "
									+ args[i]);
				}
			}
		}
		Map<String, Object> config = new HashMap<String, Object>();
		config.put(
				PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS,
				(filter != null) ? new ClasspathScanner().scanForBundles(filter
						.toString()) : new ClasspathScanner().scanForBundles());
		new PojoServiceRegistryFactoryImpl().newPojoServiceRegistry(config);
		if (main != null) {
			int count = 0;
			if (filter != null) {
				count++;
			}
			if (main != null) {
				count++;
			}
			String[] newArgs = args;
			if (count > 0) {
				newArgs = new String[args.length - count];
				System.arraycopy(args, count, newArgs, 0, newArgs.length);
			}
			main.getMethod("main", String[].class).invoke(null,
					(Object) newArgs);
		}
	}

	public BundleContext getBundleContext() {
		return m_context;
	}

	public void addServiceListener(ServiceListener listener, String filter)
			throws InvalidSyntaxException {
		m_context.addServiceListener(listener, filter);
	}

	public void addServiceListener(ServiceListener listener) {
		m_context.addServiceListener(listener);
	}

	public void removeServiceListener(ServiceListener listener) {
		m_context.removeServiceListener(listener);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <S> ServiceRegistration<S> registerService(String[] clazzes,
			S service, Dictionary<String, ?> properties) {
		return (ServiceRegistration<S>) m_context.registerService(clazzes,
				service, properties);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <S> ServiceRegistration<S> registerService(String clazz, S service,
			Dictionary<String, ?> properties) {
		return (ServiceRegistration<S>) m_context.registerService(clazz,
				service, properties);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <S> ServiceReference<S>[] getServiceReferences(String clazz,
			String filter) throws InvalidSyntaxException {
		return (ServiceReference<S>[]) m_context.getServiceReferences(clazz,
				filter);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <S> ServiceReference<S> getServiceReference(String clazz) {
		return (ServiceReference<S>) m_context.getServiceReference(clazz);
	}

	@Override
	public <S> S getService(ServiceReference<S> reference) {
		return m_context.getService(reference);
	}

	@Override
	public <S> boolean ungetService(ServiceReference<S> reference) {
		return m_context.ungetService(reference);
	}

}
